local jass = require 'jass.common'
local base64 = require 'util.base64'
local player = require 'types.player'
local hash_len = 7

local mt = {}
mt.file_count = 0
mt.file_path = {}
mt.file_info = {}
mt.only_path = {}
mt.only_info = {}
mt.data_max = {}

local function uint32_t (int)
    return int & 0xffffffff
end

local function hash_hex(str,pass)
    local pass = jass.StringHash(pass)
    local hash = uint32_t(pass)
    local length = str:len()
    for i = 1,length do
        local byte = str:sub(i,i):byte()
        hash = uint32_t(uint32_t(uint32_t(hash << 5) + hash) + byte)
    end

    local str = string.pack("I4", hash)

    local s = {}

    for i = 1, #str do
        local c = str:sub(i, i):byte()
        s[#s + 1] = string.format("%02x", c)
    end

    return table.concat(s)
end


--读取存档数值
function player.__index:dz_get2(key,default)
    local data = self._file_data or {}
    return data[key] or default or 0
end

function player.__index:dz_get(key,default)
    local data = self._file_data or {}
    if not mt.only_path[key] then
        log.error('未注册存档',key)
    end
    return data[key] or default or 0
end

--设置存档数值
function player.__index:dz_set(key,value)
    local data = self._file_data or {}
    local wait = self._wait_data or {}
    local max = mt.data_max[key]
    if value<0 then
        print('保存负数数据',key,value)
        return 0
    end
    if max and value>max then
        print('保存数据超上限',key,value,mt.data_max[key])
        return 0
    end
    data[key] = value
    self._wait_data[#self._wait_data + 1] = key
    return value
end

--增加存档数值
function player.__index:dz_add(key,value)
    local num = self:dz_get(key)
    return self:dz_set(key,num + value)
end

--合并存档数据
--保存刀官方服务器
function player.__index:merge_file(path,default)
    local data = self._file_data or {}
    local info = mt.only_info[path]
    if not info then
        print('不存在存档信息',path,index)
        return
    end

    local s = {}
    for i,v in ipairs(info) do
        local key = v[2]
        local fmt = v[3]
        local num = tonumber(data[key] or 0) or 0
        local value = default or num
        if value == nil then 
            value = 0
        end 
        value = 0xffffffff & math.modf(value)
        s[#s + 1] = string.pack(fmt,value)
    end
    
    local str = table.concat(s)
    local score = base64.encode(str)

    local hash = hash_hex(str,('%s_%s'):format(self.uuid,path))

    local real = score .. hash:sub(1,hash_len)
    if not self:get_data('存档作弊') then
        self:save_server(path,real)
        if package.local_test then
            print('保存内容',path,real,#real,hash)
        end
    end
    return real
end


--解析数据
local function load_data(player,path)
    local score = player:load_server(path) or ''
    local info = mt.only_info[path]
    if score == nil or score:len() ~= 63 then --没存过 存个初始值  或者长度不对
        score = player:merge_file(path, 0)
        print('初始化',path,score == nil or score:len())
    end
    local real = score:sub(1, 56)           --前面56字节是 base64
    local hash = score:sub(-hash_len, -1)          --后面8字节是 哈希值验证码

    local data = base64.decode(real) 
    local pass = hash_hex(data,('%s_%s'):format(player.uuid,path)):sub(1,hash_len)

    --如果尺寸不匹配的话 就是错误数据   跟哈希值不匹配 则表示这段数据是被篡改过的
    if  pass ~= hash then 
        player:merge_file(path, 0)
        player:set_data('存档作弊',true)
        player:send_msg(('[存档异常]代码：%s'):format(path))
        print('哈希异常1',path,pass,hash, real)
        return 
    end 

    --拆分数据
    local index = 1
    for _,v in ipairs(info) do
        local len = v[1]
        local key = v[2]
        local fmt = v[3]
        local str = data:sub(index, index + len - 1)
        local int = string.unpack(fmt, str)
        index = index + len

        player._file_data[key] = int
    end
end

--提交数据
ac.loop(1000,function()
    ac.each_player(function(player)
        local locks = {}
        local wait = player._wait_data
        if not wait or #wait==0 or player:get_data('存档作弊') then
            return
        end
        player._wait_data = {}
        
        --动态保存
        for i,key in ipairs(wait) do
            local path = mt.only_path[key]
            if path and not locks[path] then
                locks[path] = true
                player:merge_file(path)
            end
        end
    end)
end)


--创建存档
ac.game:event '玩家-初始化算法'(function(_,player)
    player._wait_data = {}
    player._file_data = {}
    for i,path in ipairs(mt.file_path) do
        load_data(player,path)
        --print(path)
    end
    print('使用存档',#mt.file_path)
    

    player:notify('玩家-官方存档读取完毕', player)
end)



--注册存档内容1
local fmts = {"I1","I2","I3","I4"}
ac.register_dzscore1 = function (path,info)
    local sum = 40
    mt.only_info[path] = info
    
    table.insert(mt.file_path,path)
    table.insert(mt.file_info,info)

    --统计数据大小
    for i,data in ipairs(info) do
        local len = data[1]
        local key = data[2]
        data[3] = fmts[len]
        if mt.only_path[key] then
            print('存档命名冲突',path,i,key,mt.only_path[key])
        end
        sum = sum - len

        --数值上限
        mt.data_max[key] = math.ceil(2^(8*len)) - 1

        --对应路劲
        mt.only_path[key] = path
    end
    --填充无用数据
    while sum>0 do
        local len = math.min(sum,4)
        local data = {len,'无用数据',fmts[len]}
        table.insert(info,data)
        sum = sum - len
    end
    if sum~=0 then
        print('存档长度超标',path,sum)
    end
end


--注册存档内容2
--统一大小key
ac.register_dzscore2 = function (path,size,keys)
    local size = size or 4
    local len = math.floor(40/size)
    local num = math.ceil(#keys/len)
    for a=1,num do
        local real_path = ('%s_%s'):format(path,a)
        local real_info = {}
        for b=1,len do
            local key = keys[(a-1)*len + b]
            if key then
                real_info[b] = {size,key}
            else
                break
            end
        end
        ac.register_dzscore1(real_path,real_info)
    end
end

--注册存档内容2
--统一大小key,确定数量统一命名规则
--
ac.register_dzscore3 = function (path,count,size)
    local size = size or 4
    local len = math.floor(40/size)
    local num = math.ceil(count/len)
    local keys = {}
    for a=1,count do
        keys[a] = ('%s%s'):format(path,a)
    end
    for a=1,num do
        local real_path = ('%s_%s'):format(path,a)
        local real_info = {}
        for b=1,len do
            local key = keys[(a-1)*len + b]
            if key then
                real_info[b] = {size,key}
            else
                break
            end
        end
        ac.register_dzscore1(real_path,real_info)
    end
    return keys
end

